﻿using System;
using RayDen.Library.Components.Surface.Microfacet;
using RayDen.Library.Core.Primitives;

namespace RayDen.Library.Components.Surface
{

    [Serializable]
    public class DistributionBsdf : SurfaceBsdf
    {
        public override BrdfType Type
        {
            get { return BrdfType.Glossy; }
        }

        protected MicrofacetDistribution distr;

        protected IFresnelTerm fresnel;

        protected RgbSpectrum R0;

        public DistributionBsdf(MicrofacetDistribution dist,IFresnelTerm f, RgbSpectrum r0)
        {
            this.R0 = r0;
            this.distr = 
                //dist;
            new Anisotropic(50f, 10f);
            this.fresnel = f;
        }

        public DistributionBsdf(float e, RgbSpectrum r0) : this(new BlinnDistribution(e), new FresnelNoOP(), r0) {}

        public DistributionBsdf(float e, RgbSpectrum r0, IFresnelTerm frenel) : this(new BlinnDistribution(e), frenel, r0) { }


        public static float G(ref Normal N, ref Vector wo, ref Vector wi, ref Vector wh)
        {
            float NdotWh = Vector.AbsDot(ref N, ref wh);
            float NdotWo = Vector.AbsDot(ref N, wo);
            float NdotWi = Vector.AbsDot(ref N, wi);
            float WOdotWh = Vector.AbsDot( ref wo, ref wh);
            return Math.Min(1f, Math.Min(2f * NdotWh * NdotWo / WOdotWh, 2f * NdotWh * NdotWi / WOdotWh));
        } 

        public override void f(ref Vector lwo, ref Vector lwi, ref Normal N, ref RgbSpectrum in_fs, out RgbSpectrum fs)
        {
            CreateFrame(ref N);
            var wi = WorldToLocal(ref lwi);
            var wo = WorldToLocal(ref lwo);
            EvalBrdf(ref wo, ref wi, ref N, out fs);
        }

        public override float Pdf(ref Vector wo, ref Vector wi, BxDFTypes bxDFType)
        {
            if(!SameHemisphere(ref wo, ref wi))
                return 0f;
            return distr.Pdf(ref wo, ref wi);
        }

        public override void Sample_f(ref Vector lwo, ref Normal N, ref Normal shadeN, ref RgbSpectrum in_f, float u0, float u1, float u2, ref SurfaceTextureData surfaceData, out BsdfSampleData result)
        {
            result.Lambda = 0f;

            this.R0 = surfaceData.Diffuse;
            result.Type = BrdfType.Glossy;
            Vector wi;
            CreateFrame(ref shadeN);
            var wo = WorldToLocal(ref lwo);
            distr.Sample_f(ref wo, out wi, u0, u1, out result.Pdf);
            result.Wi = LocalToWorld(ref wi);

            if (!SameHemisphere(ref wo, ref wi))
                result.F =
                    RgbSpectrum.ZeroSpectrum();
                    //new RgbSpectrum(1f, 0f,0f);
            else
            {
                RgbSpectrum fs;
                EvalBrdf(ref wo, ref wi, ref shadeN, out fs);
                result.F = fs;
            }

        }

        private void EvalBrdf(ref Vector wo, ref Vector wi, ref Normal N, out RgbSpectrum fs)
        {
            float cosThetaO = Vector.AbsDot(ref N, ref wo);
            //AbsCosTheta(wo);
            float cosThetaI = Vector.AbsDot(ref N, ref wi);
            //AbsCosTheta(wi);
            if (Math.Abs(cosThetaI - 0f) < Epsilon || Math.Abs(cosThetaO - 0f) < Epsilon)
            {
                fs = new RgbSpectrum(0f, 0f, 0f);
                return;
                //return new RgbSpectrum(1f, 0f, 0f);
            }
            Vector wh = wi + wo;
            if (wh.IsZero())
            {
                fs = new RgbSpectrum(0f);
                return;
            }
            //return new RgbSpectrum(1f, 0f, 0f);
            wh = wh.Normalize();
            float cosThetaH = Vector.Dot(ref wi, ref wh);
            var F = fresnel.Evaluate(cosThetaH);

            fs = (R0 * distr.D(ref wh) * G(ref N, ref wo, ref wi, ref wh) * F / (4f * cosThetaI * cosThetaO));
        }

        public override bool IsDiffuse()
        {
            return false;
        }

        public override bool IsSpecular()
        {
            return true;
        }
    }
}